package graph;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import utils.GraphUtils;

/**
 * Represents a simple undirected graph.
 * @author Alex Dusenbery
 * @version 1.0
 *
 */
public class UndirectedGraph<V, W> implements Graph <V,W>{

	private Collection<Vertex<V>> vertices;
	
	/**
	 * Returns the vertices in this graph.
	 * @return A collection of vertices.
	 */
	public Collection<Vertex<V>> getVertices() {
		return vertices;
	}

	private Collection<Edge<V,W>> edges;
	
	/** Returns the edges in this graph
	 * @return A collection of edges.
	 */
	public Collection<Edge<V, W>> getEdges() {
		return edges;
	}
	
	@SuppressWarnings("rawtypes")
	private Set<Subgraph> partition;
	
	/**
	 * Creates a new empty graph.
	 */
	@SuppressWarnings("rawtypes")
	public UndirectedGraph() {
		vertices = new HashSet<Vertex<V>>();
		edges = new HashSet<Edge<V,W>>();
		partition = new HashSet<Subgraph>();
	}
	
	/**
	 * Creates a new graph object with the vertex and edge collections specified.
	 * @param vList
	 * @param eList
	 */
	@SuppressWarnings("rawtypes")
	public UndirectedGraph(Set<Vertex<V>> vList, Set<Edge<V,W>> eList) {
		vertices = vList;
		edges = eList;
		partition = new HashSet<Subgraph>();
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public UndirectedGraph(int numVertices, int numEdges, boolean includePartition, boolean connected) {
		GraphUtils utils = new GraphUtils();
		try {
			Graph g = utils.generateUndirectedGraph(numVertices, numEdges, connected);
			this.vertices = g.getVertices();
			this.edges = g.getEdges();
			if(includePartition)
				this.partition = utils.makePartition(g);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Adds the specified vertex to the graph.
	 * @param v
	 */
	public boolean addVertex(Vertex<V> v) {
		if(!vertices.contains(v)) {
			vertices.add(v);
			return true;
		}
		else
			return false;
	}
	
	/**
	 * Adds the specified vertex to the graph.
	 * @param v
	 */
	@SuppressWarnings("rawtypes")
	public boolean addVertex(Vertex vArray[]) {
		boolean success = true;
		for(Vertex<V> v : vArray) {
		if(!vertices.contains(v)) {
			vertices.add(v);
		}
		else
			success = false;
		}
		return success;
	}
	
	/**
	 * Returns a vertex by String name.
	 * @param name
	 * @return
	 */
	public Vertex<V> getVertex(String name)
	{
		for(Vertex<V> v: vertices)
		{
			if(v.getName().equals(name))
				return v;
		}
		return null;
		
	}
	
	/**
	 * Returns true if this graph has an edge connecting the two vertices specified.
	 * @param v1
	 * @param v2
	 * @return
	 */
	public boolean hasEdge(Vertex<V> v1, Vertex<V> v2) {
		if(edges.contains(new Edge<V,W>(v1, v2)) || edges.contains(new Edge<V,W>(v2, v1)))
			return true;
		else
			return false;
	}
	
	
	/**
	 * Adds the specified edge (and any vertices not already in the graph) to the graph object.
	 * @param e
	 */
	public void addEdge(Edge<V,W> e) {
		if(!vertices.contains(e.getV1()))
			addVertex(e.getV1());
		if(!vertices.contains(e.getV2()))
			addVertex(e.getV2());
		
		if(!edges.contains(e)) {
			edges.add(e);
		}
	}
	
	public int getEdgeCount() {
		return this.edges.size();
	}
	
	public int getVertexCount() {
		return this.vertices.size();
	}
	
	/**
	 * @return the partition
	 */
	@SuppressWarnings("rawtypes")
	public Set<Subgraph> getPartition() {
		return partition;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Vertices: ");
		for(Vertex<V> v: vertices)
			sb.append(v.toString()).append(", ");
		
		sb.append("\nEdges: ");
		for(Edge<V,W> e: edges)
			sb.append(e.toString()).append(", ");
		
		return sb.toString();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((edges == null) ? 0 : edges.hashCode());
		result = prime * result
				+ ((vertices == null) ? 0 : vertices.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof UndirectedGraph)) {
			return false;
		}
		UndirectedGraph other = (UndirectedGraph) obj;
		if (edges == null) {
			if (other.edges != null) {
				return false;
			}
		} else if (!edges.equals(other.edges)) {
			return false;
		}
		if (vertices == null) {
			if (other.vertices != null) {
				return false;
			}
		} else if (!vertices.equals(other.vertices)) {
			return false;
		}
		return true;
	}
	
	
}
